{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 视频动作识别\n",
    "视频动作识别是指对一小段视频中的内容进行分析，判断视频中的人物做了哪种动作。视频动作识别与图像领域的图像识别，既有联系又有区别，图像识别是对一张静态图片进行识别，而视频动作识别不仅要考察每张图片的静态内容，还要考察不同图片静态内容之间的时空关系。比如一个人扶着一扇半开的门，仅凭这一张图片无法判断该动作是开门动作还是关门动作。\n",
    "\n",
    "视频分析领域的研究相比较图像分析领域的研究，发展时间更短，也更有难度。视频分析模型完成的难点首先在于，需要强大的计算资源来完成视频的分析。视频要拆解成为图像进行分析，导致模型的数据量十分庞大。视频内容有很重要的考虑因素是动作的时间顺序，需要将视频转换成的图像通过时间关系联系起来，做出判断，所以模型需要考虑时序因素，加入时间维度之后参数也会大量增加。\n",
    "\n",
    "得益于PASCAL VOC、ImageNet、MS COCO等数据集的公开，图像领域产生了很多的经典模型，那么在视频分析领域有没有什么经典的模型呢？答案是有的，本案例将为大家介绍视频动作识别领域的经典模型并进行代码实践。\n",
    "\n",
    "由于本案例的代码是在华为云ModelArts Notebook上运行，所以需要先按照如下步骤来进行Notebook环境的准备。\n",
    "\n",
    "### 进入ModelArts\n",
    "\n",
    "点击如下链接：https://www.huaweicloud.com/product/modelarts.html ， 进入ModelArts主页。点击“立即使用”按钮，输入用户名和密码登录，进入ModelArts使用页面。\n",
    "\n",
    "### 进入ModelArts\n",
    "\n",
    "点击如下链接：https://www.huaweicloud.com/product/modelarts.html ， 进入ModelArts主页。点击“立即使用”按钮，输入用户名和密码登录，进入ModelArts使用页面。\n",
    "\n",
    "### 创建ModelArts notebook\n",
    "\n",
    "下面，我们在ModelArts中创建一个notebook开发环境，ModelArts notebook提供网页版的Python开发环境，可以方便的编写、运行代码，并查看运行结果。\n",
    "\n",
    "第一步：在ModelArts服务主界面依次点击“开发环境”、“创建”\n",
    "\n",
    "![create_nb_create_button](./img/create_nb_create_button.png)\n",
    "\n",
    "第二步：填写notebook所需的参数：\n",
    "\n",
    "![jupyter](./img/notebook1.png)\n",
    "\n",
    "第三步：配置好notebook参数后，点击下一步，进入notebook信息预览。确认无误后，点击“立即创建”\n",
    "![jupyter](./img/notebook2.png)\n",
    "\n",
    "第四步：创建完成后，返回开发环境主界面，等待Notebook创建完毕后，打开Notebook，进行下一步操作。\n",
    "![modelarts_notebook_index](./img/modelarts_notebook_index.png)\n",
    "\n",
    "### 在ModelArts中创建开发环境\n",
    "\n",
    "接下来，我们创建一个实际的开发环境，用于后续的实验步骤。\n",
    "\n",
    "第一步：点击下图所示的“启动”按钮，加载后“打开”按钮变从灰色变为蓝色后点击“打开”进入刚刚创建的Notebook\n",
    "![jupyter](./img/notebook3.png)\n",
    "![jupyter](./img/notebook4.png)\n",
    "\n",
    "\n",
    "第二步：创建一个Python3环境的的Notebook。点击右上角的\"New\"，然后选择TensorFlow 1.13.1开发环境。\n",
    "\n",
    "第三步：点击左上方的文件名\"Untitled\"，并输入一个与本实验相关的名称，如\"action_recognition\"\n",
    "![notebook_untitled_filename](./img/notebook_untitled_filename.png)\n",
    "![notebook_name_the_ipynb](./img/notebook_name_the_ipynb.png)\n",
    "\n",
    "\n",
    "### 在Notebook中编写并执行代码\n",
    "\n",
    "在Notebook中，我们输入一个简单的打印语句，然后点击上方的运行按钮，可以查看语句执行的结果：\n",
    "![run_helloworld](./img/run_helloworld.png)\n",
    "\n",
    "\n",
    "开发环境准备好啦，接下来可以愉快地写代码啦！"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 准备源代码和数据\n",
    "\n",
    "这一步准备案例所需的源代码和数据，相关资源已经保存在OBS中，我们通过[ModelArts SDK](https://support.huaweicloud.com/sdkreference-modelarts/modelarts_04_0002.html)将资源下载到本地，并解压到当前目录下。解压后，当前目录包含data、dataset_subset和其他目录文件，分别是预训练参数文件、数据集和代码文件等。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "if not os.path.exists('videos'):\n",
    "    from modelarts.session import Session\n",
    "    session = Session()\n",
    "    session.download_data(bucket_path=\"ai-course-common-26-bj4/video/video.tar.gz\", path=\"./video.tar.gz\")\n",
    "    # 使用tar命令解压资源包\n",
    "    os.system(\"tar xf ./video.tar.gz\")\n",
    "    # 使用rm命令删除压缩包\n",
    "    os.system(\"rm ./video.tar.gz\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "上一节课我们已经介绍了视频动作识别有HMDB51、UCF-101和Kinetics三个常用的数据集，本案例选用了UCF-101数据集的部分子集作为演示用数据集，接下来，我们播放一段UCF-101中的视频："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "video_name = \"./data/v_TaiChi_g01_c01.avi\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from IPython.display import clear_output, Image, display, HTML\n",
    "import time\n",
    "import cv2\n",
    "import base64\n",
    "import numpy as np\n",
    "\n",
    "def arrayShow(img):\n",
    "    _,ret = cv2.imencode('.jpg', img) \n",
    "    return Image(data=ret) \n",
    "\n",
    "cap = cv2.VideoCapture(video_name)\n",
    "\n",
    "while True:\n",
    "    try:\n",
    "        clear_output(wait=True)\n",
    "        ret, frame = cap.read()\n",
    "        if ret:\n",
    "            tmp = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)\n",
    "            img = arrayShow(frame)\n",
    "            display(img)\n",
    "            time.sleep(0.05)\n",
    "        else:\n",
    "            break\n",
    "    except KeyboardInterrupt:\n",
    "        cap.release()\n",
    "cap.release()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 视频动作识别模型介绍\n",
    "\n",
    "在图像领域中，ImageNet作为一个大型图像识别数据集，自2010年开始，使用此数据集训练出的图像算法层出不穷，深度学习模型经历了从AlexNet到VGG-16再到更加复杂的结构，模型的表现也越来越好。在识别千种类别的图片时，错误率表现如下：\n",
    "\n",
    "<img src=\"./img/ImageNet.png\" width=\"500\" height=\"500\" align=center>\n",
    "\n",
    "在图像识别中表现很好的模型，可以在图像领域的其他任务中继续使用，通过复用模型中部分层的参数，就可以提升模型的训练效果。有了基于ImageNet模型的图像模型，很多模型和任务都有了更好的训练基础，比如说物体检测、实例分割、人脸检测、人脸识别等。\n",
    "\n",
    "那么训练效果显著的图像模型是否可以用于视频模型的训练呢？答案是yes，有研究证明，在视频领域，如果能够复用图像模型结构，甚至参数，将对视频模型的训练有很大帮助。但是怎样才能复用上图像模型的结构呢？首先需要知道视频分类与图像分类的不同，如果将视频视作是图像的集合，每一个帧将作为一个图像，视频分类任务除了要考虑到图像中的表现，也要考虑图像间的时空关系，才可以对视频动作进行分类。\n",
    "\n",
    "为了捕获图像间的时空关系，论文[I3D](https://arxiv.org/pdf/1705.07750.pdf)介绍了三种旧的视频分类模型，并提出了一种更有效的Two-Stream Inflated 3D ConvNets（简称I3D）的模型，下面将逐一简介这四种模型，更多细节信息请查看原论文。\n",
    "\n",
    "### 旧模型一：卷积网络+LSTM\n",
    "\n",
    "模型使用了训练成熟的图像模型，通过卷积网络，对每一帧图像进行特征提取、池化和预测，最后在模型的末端加一个LSTM层（长短期记忆网络），如下图所示，这样就可以使模型能够考虑时间性结构，将上下文特征联系起来，做出动作判断。这种模型的缺点是只能捕获较大的工作，对小动作的识别效果较差，而且由于视频中的每一帧图像都要经过网络的计算，所以训练时间很长。\n",
    "\n",
    "<img src=\"./img/video_model_0.png\" width=\"200\" height=\"200\" align=center>\n",
    "\n",
    "### 旧模型二：3D卷积网络\n",
    "\n",
    "3D卷积类似于2D卷积，将时序信息加入卷积操作。虽然这是一种看起来更加自然的视频处理方式，但是由于卷积核维度增加，参数的数量也增加了，模型的训练变得更加困难。这种模型没有对图像模型进行复用，而是直接将视频数据传入3D卷积网络进行训练。\n",
    "\n",
    "<img src=\"./img/video_model_1.png\" width=\"150\" height=\"150\" align=center>\n",
    "\n",
    "### 旧模型三：Two-Stream 网络\n",
    "\n",
    "Two-Stream 网络的两个流分别为**1张RGB快照**和**10张计算之后的光流帧画面组成的栈**。两个流都通过ImageNet预训练好的图像卷积网络，光流部分可以分为竖直和水平两个通道，所以是普通图片输入的2倍，模型在训练和测试中表现都十分出色。\n",
    "\n",
    "<img src=\"./img/video_model_2.png\" width=\"400\" height=\"400\" align=center>\n",
    "\n",
    "#### 光流视频 optical flow video\n",
    "\n",
    "上面讲到了光流，在此对光流做一下介绍。光流是什么呢？名字很专业，感觉很陌生，但实际上这种视觉现象我们每天都在经历，我们坐高铁的时候，可以看到窗外的景物都在快速往后退，开得越快，就感受到外面的景物就是“刷”地一个残影，这种视觉上目标的运动方向和速度就是光流。光流从概念上讲，是对物体运动的观察，通过找到相邻帧之间的相关性来判断帧之间的对应关系，计算出相邻帧画面中物体的运动信息，获取像素运动的瞬时速度。在原始视频中，有运动部分和静止的背景部分，我们通常需要判断的只是视频中运动部分的状态，而光流就是通过计算得到了视频中运动部分的运动信息。\n",
    "\n",
    "下面是一个经过计算后的原视频及光流视频。\n",
    "\n",
    "原视频\n",
    "![See videos/v_CricketShot_g04_c01_rgb.gif](./img/v_CricketShot_g04_c01_rgb.gif)\n",
    "光流视频\n",
    "![See videos/v_CricketShot_g04_c01_flow.gif](./img/v_CricketShot_g04_c01_flow.gif)\n",
    "\n",
    "### 新模型：Two-Stream Inflated 3D ConvNets\n",
    "\n",
    "新模型采取了以下几点结构改进：\n",
    "   - 拓展2D卷积为3D。直接利用成熟的图像分类模型，只不过将网络中二维$ N × N $的 filters 和 pooling kernels 直接变成$ N × N × N $；\n",
    "   - 用 2D filter 的预训练参数来初始化 3D filter 的参数。上一步已经利用了图像分类模型的网络，这一步的目的是能利用上网络的预训练参数，直接将 2D filter 的参数直接沿着第三个时间维度进行复制N次，最后将所有参数值再除以N；\n",
    "   - 调整感受野的形状和大小。新模型改造了图像分类模型Inception-v1的结构，前两个max-pooling层改成使用$ 1 × 3 × 3 $kernels and stride 1 in time，其他所有max-pooling层都仍然使用对此的kernel和stride，最后一个average pooling层使用$ 2 × 7 × 7 $的kernel。\n",
    "   - 延续了Two-Stream的基本方法。用双流结构来捕获图片之间的时空关系仍然是有效的。\n",
    "\n",
    "最后新模型的整体结构如下图所示：\n",
    "\n",
    "<img src=\"./img/video_model_3.png\" width=\"200\" height=\"200\" align=center>\n",
    "\n",
    "好，到目前为止，我们已经讲解了视频动作识别的经典数据集和经典模型，下面我们通过代码来实践地跑一跑其中的两个模型：**C3D模型**（ 3D卷积网络）以及**I3D模型**（Two-Stream Inflated 3D ConvNets）。\n",
    "\n",
    "### C3D模型结构\n",
    "\n",
    "\n",
    "我们已经在前面的“旧模型二：3D卷积网络”中讲解到3D卷积网络是一种看起来比较自然的处理视频的网络，虽然它有效果不够好，计算量也大的特点，但它的结构很简单，可以构造一个很简单的网络就可以实现视频动作识别，如下图所示是3D卷积的示意图：\n",
    "\n",
    "![image.png](./img/c3d_0.png)\n",
    "\n",
    "a)中，一张图片进行了2D卷积， b)中，对视频进行2D卷积，将多个帧视作多个通道， c)中，对视频进行3D卷积，将时序信息加入输入信号中。\n",
    "\n",
    "ab中，output都是一张二维特征图，所以无论是输入是否有时间信息，输出都是一张二维的特征图，2D卷积失去了时序信息。只有3D卷积在输出时，保留了时序信息。2D和3D池化操作同样有这样的问题。\n",
    "\n",
    "如下图所示是一种[C3D](https://www.cv-foundation.org/openaccess/content_iccv_2015/papers/Tran_Learning_Spatiotemporal_Features_ICCV_2015_paper.pdf)网络的变种：（如需阅读原文描述，请查看I3D论文 2.2 节）\n",
    "\n",
    "![image.png](./img/c3d_1.png)\n",
    "\n",
    "C3D结构，包括8个卷积层，5个最大池化层以及2个全连接层，最后是softmax输出层。\n",
    "\n",
    "所有的3D卷积核为$ 3 × 3 × 3$ 步长为1，使用SGD，初始学习率为0.003，每150k个迭代，除以2。优化在1.9M个迭代的时候结束，大约13epoch。\n",
    "\n",
    "数据处理时，视频抽帧定义大小为：$ c × l × h × w$，$c$为通道数量，$l$为帧的数量，$h$为帧画面的高度，$w$为帧画面的宽度。3D卷积核和池化核的大小为$ d × k × k$，$d$是核的时间深度，$k$是核的空间大小。网络的输入为视频的抽帧，预测出的是类别标签。所有的视频帧画面都调整大小为$ 128 × 171 $，几乎将UCF-101数据集中的帧调整为一半大小。视频被分为不重复的16帧画面，这些画面将作为模型网络的输入。最后对帧画面的大小进行裁剪，输入的数据为$16 × 112 × 112 $"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### C3D模型训练\n",
    "接下来，我们将对C3D模型进行训练，训练过程分为：数据预处理以及模型训练。在此次训练中，我们使用的数据集为UCF-101，由于C3D模型的输入是视频的每帧图片，因此我们需要对数据集的视频进行抽帧，也就是将视频转换为图片，然后将图片数据传入模型之中，进行训练。\n",
    "\n",
    "在本案例中，我们随机抽取了UCF-101数据集的一部分进行训练的演示，感兴趣的同学可以下载完整的UCF-101数据集进行训练。\n",
    "\n",
    "[UCF-101下载](https://www.crcv.ucf.edu/data/UCF101.php)\n",
    "\n",
    "数据集存储在目录` dataset_subset`下\n",
    "\n",
    "如下代码是使用cv2库进行视频文件到图片文件的转换"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "import cv2\n",
    "import os\n",
    "# 视频数据集存储位置\n",
    "video_path = './dataset_subset/'\n",
    "# 生成的图像数据集存储位置\n",
    "save_path = './dataset/'\n",
    "# 如果文件路径不存在则创建路径\n",
    "if not os.path.exists(save_path):\n",
    "    os.mkdir(save_path)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 获取动作列表\n",
    "action_list = os.listdir(video_path)\n",
    "# 遍历所有动作\n",
    "for action in action_list:\n",
    "    if action.startswith(\".\")==False:\n",
    "        if not os.path.exists(save_path+action):\n",
    "            os.mkdir(save_path+action)\n",
    "        video_list = os.listdir(video_path+action)\n",
    "        # 遍历所有视频\n",
    "        for video in video_list:\n",
    "            prefix = video.split('.')[0]\n",
    "            if not os.path.exists(os.path.join(save_path, action, prefix)):\n",
    "                os.mkdir(os.path.join(save_path, action, prefix))\n",
    "            save_name = os.path.join(save_path, action, prefix) + '/'\n",
    "            video_name = video_path+action+'/'+video\n",
    "            # 读取视频文件\n",
    "            # cap为视频的帧\n",
    "            cap = cv2.VideoCapture(video_name)\n",
    "            # fps为帧率\n",
    "            fps = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))\n",
    "            fps_count = 0\n",
    "            for i in range(fps):\n",
    "                ret, frame = cap.read()\n",
    "                if ret:\n",
    "                    # 将帧画面写入图片文件中\n",
    "                    cv2.imwrite(save_name+str(10000+fps_count)+'.jpg',frame)\n",
    "                    fps_count += 1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "此时，视频逐帧转换成的图片数据已经存储起来，为模型训练做准备。\n",
    "\n",
    "### 模型训练\n",
    "首先，我们构建模型结构。\n",
    "\n",
    "C3D模型结构我们之前已经介绍过，这里我们通过`keras`提供的Conv3D，MaxPool3D，ZeroPadding3D等函数进行模型的搭建。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Using TensorFlow backend.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "WARNING:tensorflow:From /home/ma-user/anaconda3/envs/TensorFlow-1.13.1/lib/python3.6/site-packages/tensorflow/python/framework/op_def_library.py:263: colocate_with (from tensorflow.python.framework.ops) is deprecated and will be removed in a future version.\n",
      "Instructions for updating:\n",
      "Colocations handled automatically by placer.\n",
      "WARNING:tensorflow:From /home/ma-user/anaconda3/envs/TensorFlow-1.13.1/lib/python3.6/site-packages/keras/backend/tensorflow_backend.py:3445: calling dropout (from tensorflow.python.ops.nn_ops) with keep_prob is deprecated and will be removed in a future version.\n",
      "Instructions for updating:\n",
      "Please use `rate` instead of `keep_prob`. Rate should be set to `rate = 1 - keep_prob`.\n"
     ]
    }
   ],
   "source": [
    "from keras.layers import Dense,Dropout,Conv3D,Input,MaxPool3D,Flatten,Activation, ZeroPadding3D\n",
    "from keras.regularizers import l2\n",
    "from keras.models import Model, Sequential\n",
    "\n",
    "# 输入数据为 112×112 的图片，16帧， 3通道\n",
    "input_shape = (112,112,16,3)\n",
    "# 权重衰减率\n",
    "weight_decay = 0.005\n",
    "# 类型数量，我们使用UCF-101 为数据集，所以为101\n",
    "nb_classes = 101\n",
    "\n",
    "# 构建模型结构\n",
    "inputs = Input(input_shape)\n",
    "\n",
    "x = Conv3D(64,(3,3,3),strides=(1,1,1),padding='same',\n",
    "           activation='relu',kernel_regularizer=l2(weight_decay))(inputs)\n",
    "x = MaxPool3D((2,2,1),strides=(2,2,1),padding='same')(x)\n",
    "\n",
    "x = Conv3D(128,(3,3,3),strides=(1,1,1),padding='same',\n",
    "           activation='relu',kernel_regularizer=l2(weight_decay))(x)\n",
    "x = MaxPool3D((2,2,2),strides=(2,2,2),padding='same')(x)\n",
    "\n",
    "x = Conv3D(128,(3,3,3),strides=(1,1,1),padding='same',\n",
    "           activation='relu',kernel_regularizer=l2(weight_decay))(x)\n",
    "x = MaxPool3D((2,2,2),strides=(2,2,2),padding='same')(x)\n",
    "\n",
    "x = Conv3D(256,(3,3,3),strides=(1,1,1),padding='same',\n",
    "           activation='relu',kernel_regularizer=l2(weight_decay))(x)\n",
    "x = MaxPool3D((2,2,2),strides=(2,2,2),padding='same')(x)\n",
    "\n",
    "x = Conv3D(256, (3, 3, 3), strides=(1, 1, 1), padding='same',\n",
    "           activation='relu',kernel_regularizer=l2(weight_decay))(x)\n",
    "x = MaxPool3D((2, 2, 2), strides=(2, 2, 2), padding='same')(x)\n",
    "\n",
    "x = Flatten()(x)\n",
    "x = Dense(2048,activation='relu',kernel_regularizer=l2(weight_decay))(x)\n",
    "x = Dropout(0.5)(x)\n",
    "x = Dense(2048,activation='relu',kernel_regularizer=l2(weight_decay))(x)\n",
    "x = Dropout(0.5)(x)\n",
    "x = Dense(nb_classes,kernel_regularizer=l2(weight_decay))(x)\n",
    "x = Activation('softmax')(x)\n",
    "\n",
    "model = Model(inputs, x)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "通过keras提供的`summary()`方法，打印模型结构。可以看到模型的层构建以及各层的输入输出情况。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "_________________________________________________________________\n",
      "Layer (type)                 Output Shape              Param #   \n",
      "=================================================================\n",
      "input_1 (InputLayer)         (None, 112, 112, 16, 3)   0         \n",
      "_________________________________________________________________\n",
      "conv3d_1 (Conv3D)            (None, 112, 112, 16, 64)  5248      \n",
      "_________________________________________________________________\n",
      "max_pooling3d_1 (MaxPooling3 (None, 56, 56, 16, 64)    0         \n",
      "_________________________________________________________________\n",
      "conv3d_2 (Conv3D)            (None, 56, 56, 16, 128)   221312    \n",
      "_________________________________________________________________\n",
      "max_pooling3d_2 (MaxPooling3 (None, 28, 28, 8, 128)    0         \n",
      "_________________________________________________________________\n",
      "conv3d_3 (Conv3D)            (None, 28, 28, 8, 128)    442496    \n",
      "_________________________________________________________________\n",
      "max_pooling3d_3 (MaxPooling3 (None, 14, 14, 4, 128)    0         \n",
      "_________________________________________________________________\n",
      "conv3d_4 (Conv3D)            (None, 14, 14, 4, 256)    884992    \n",
      "_________________________________________________________________\n",
      "max_pooling3d_4 (MaxPooling3 (None, 7, 7, 2, 256)      0         \n",
      "_________________________________________________________________\n",
      "conv3d_5 (Conv3D)            (None, 7, 7, 2, 256)      1769728   \n",
      "_________________________________________________________________\n",
      "max_pooling3d_5 (MaxPooling3 (None, 4, 4, 1, 256)      0         \n",
      "_________________________________________________________________\n",
      "flatten_1 (Flatten)          (None, 4096)              0         \n",
      "_________________________________________________________________\n",
      "dense_1 (Dense)              (None, 2048)              8390656   \n",
      "_________________________________________________________________\n",
      "dropout_1 (Dropout)          (None, 2048)              0         \n",
      "_________________________________________________________________\n",
      "dense_2 (Dense)              (None, 2048)              4196352   \n",
      "_________________________________________________________________\n",
      "dropout_2 (Dropout)          (None, 2048)              0         \n",
      "_________________________________________________________________\n",
      "dense_3 (Dense)              (None, 101)               206949    \n",
      "_________________________________________________________________\n",
      "activation_1 (Activation)    (None, 101)               0         \n",
      "=================================================================\n",
      "Total params: 16,117,733\n",
      "Trainable params: 16,117,733\n",
      "Non-trainable params: 0\n",
      "_________________________________________________________________\n"
     ]
    }
   ],
   "source": [
    "model.summary()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "通过keras的`input`方法可以查看模型的输入形状，shape分别为`( batch size, width, height, frames, channels) ` 。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor 'input_1:0' shape=(?, 112, 112, 16, 3) dtype=float32>"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "model.input"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "可以看到模型的数据处理的维度与图像处理模型有一些差别，多了frames维度，体现出时序关系在视频分析中的影响。\n",
    "\n",
    "接下来，我们开始将图片文件转为训练需要的数据形式。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 引用必要的库\n",
    "from keras.optimizers import SGD,Adam\n",
    "from keras.utils import np_utils\n",
    "\n",
    "import numpy as np\n",
    "import random\n",
    "import cv2\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "# 自定义callbacks\n",
    "from schedules import onetenth_4_8_12"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "参数定义"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "img_path = save_path  # 图片文件存储位置\n",
    "results_path = './results'  # 训练结果保存位置\n",
    "if not os.path.exists(results_path):\n",
    "    os.mkdir(results_path)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "数据集划分，随机抽取4/5 作为训练集，其余为验证集。将文件信息分别存储在`train_list`和`test_list`中，为训练做准备。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "训练集为：\n",
      "['v_Bowling_g05_c02', 'v_Bowling_g06_c02', 'v_Bowling_g05_c03', 'v_Bowling_g06_c07', 'v_Bowling_g01_c05', 'v_Bowling_g03_c05', 'v_Bowling_g03_c04', 'v_Bowling_g01_c07', 'v_Bowling_g02_c02', 'v_Bowling_g05_c06', 'v_Bowling_g03_c06', 'v_Bowling_g04_c01', 'v_Bowling_g03_c02', 'v_Bowling_g03_c03', 'v_Bowling_g06_c04', 'v_Bowling_g02_c04', 'v_Bowling_g03_c07', 'v_Bowling_g02_c03', 'v_Bowling_g05_c04', 'v_Bowling_g06_c05', 'v_Bowling_g01_c06', 'v_Bowling_g06_c06', 'v_Bowling_g05_c05', 'v_Bowling_g03_c01', 'v_Fencing_g10_c04', 'v_Fencing_g09_c01', 'v_Fencing_g11_c02', 'v_Fencing_g11_c05', 'v_Fencing_g09_c06', 'v_Fencing_g15_c01', 'v_Fencing_g14_c04', 'v_Fencing_g10_c05', 'v_Fencing_g10_c03', 'v_Fencing_g11_c04', 'v_Fencing_g13_c02', 'v_Fencing_g10_c01', 'v_Fencing_g12_c01', 'v_Fencing_g11_c01', 'v_Fencing_g09_c05', 'v_Fencing_g13_c01', 'v_Fencing_g12_c02', 'v_Fencing_g12_c04', 'v_Fencing_g09_c02', 'v_Fencing_g14_c02', 'v_Fencing_g13_c03', 'v_Fencing_g12_c03', 'v_Fencing_g09_c04', 'v_Fencing_g12_c05', 'v_ApplyEyeMakeup_g03_c04', 'v_ApplyEyeMakeup_g03_c02', 'v_ApplyEyeMakeup_g03_c05', 'v_ApplyEyeMakeup_g02_c01', 'v_ApplyEyeMakeup_g01_c03', 'v_ApplyEyeMakeup_g04_c07', 'v_ApplyEyeMakeup_g06_c01', 'v_ApplyEyeMakeup_g03_c06', 'v_ApplyEyeMakeup_g02_c03', 'v_ApplyEyeMakeup_g04_c06', 'v_ApplyEyeMakeup_g04_c04', 'v_ApplyEyeMakeup_g04_c02', 'v_ApplyEyeMakeup_g01_c05', 'v_ApplyEyeMakeup_g04_c01', 'v_ApplyEyeMakeup_g05_c03', 'v_ApplyEyeMakeup_g02_c02', 'v_ApplyEyeMakeup_g05_c02', 'v_ApplyEyeMakeup_g01_c06', 'v_ApplyEyeMakeup_g03_c03', 'v_ApplyEyeMakeup_g05_c07', 'v_ApplyEyeMakeup_g04_c05', 'v_ApplyEyeMakeup_g03_c01', 'v_ApplyEyeMakeup_g06_c02', 'v_ApplyEyeMakeup_g04_c03']\n",
      "共72 个视频\n",
      "\n",
      "验证集为：\n",
      "['v_Bowling_g05_c07', 'v_Bowling_g06_c03', 'v_Bowling_g04_c03', 'v_Bowling_g04_c02', 'v_Bowling_g06_c01', 'v_Bowling_g05_c01', 'v_Bowling_g04_c04', 'v_Bowling_g02_c01', 'v_Fencing_g08_c03', 'v_Fencing_g08_c04', 'v_Fencing_g10_c02', 'v_Fencing_g14_c01', 'v_Fencing_g11_c03', 'v_Fencing_g13_c04', 'v_Fencing_g09_c03', 'v_Fencing_g14_c03', 'v_ApplyEyeMakeup_g05_c01', 'v_ApplyEyeMakeup_g05_c04', 'v_ApplyEyeMakeup_g01_c02', 'v_ApplyEyeMakeup_g05_c05', 'v_ApplyEyeMakeup_g01_c01', 'v_ApplyEyeMakeup_g02_c04', 'v_ApplyEyeMakeup_g05_c06', 'v_ApplyEyeMakeup_g01_c04']\n",
      "共24 个视频\n"
     ]
    }
   ],
   "source": [
    "cates = os.listdir(img_path)\n",
    "train_list = []\n",
    "test_list = []\n",
    "# 遍历所有的动作类型\n",
    "for cate in cates:\n",
    "    videos = os.listdir(os.path.join(img_path, cate))\n",
    "    length = len(videos)//5\n",
    "    # 训练集大小，随机取视频文件加入训练集\n",
    "    train= random.sample(videos, length*4)\n",
    "    train_list.extend(train)\n",
    "    # 将余下的视频加入测试集\n",
    "    for video in videos:\n",
    "        if video not in train:\n",
    "            test_list.append(video)\n",
    "print(\"训练集为：\")    \n",
    "print( train_list)\n",
    "print(\"共%d 个视频\\n\"%(len(train_list)))\n",
    "print(\"验证集为：\")            \n",
    "print(test_list)\n",
    "print(\"共%d 个视频\"%(len(test_list)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "接下来开始进行模型的训练。\n",
    "\n",
    "首先定义数据读取方法。方法`process_data`中读取一个batch的数据，包含16帧的图片信息的数据，以及数据的标注信息。在读取图片数据时，对图片进行随机裁剪和翻转操作以完成数据增广。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "def process_data(img_path, file_list,batch_size=16,train=True):\n",
    "    batch = np.zeros((batch_size,16,112,112,3),dtype='float32')\n",
    "    labels = np.zeros(batch_size,dtype='int')\n",
    "    cate_list = os.listdir(img_path)\n",
    "    \n",
    "    def read_classes():\n",
    "        path = \"./classInd.txt\"\n",
    "        with open(path, \"r+\") as f:\n",
    "            lines = f.readlines()\n",
    "        classes = {}\n",
    "        for line in lines:\n",
    "            c_id = line.split()[0]\n",
    "            c_name = line.split()[1]\n",
    "            classes[c_name] =c_id \n",
    "        return classes\n",
    "        \n",
    "    classes_dict = read_classes()\n",
    "    \n",
    "    for file in file_list:\n",
    "        cate = file.split(\"_\")[1]\n",
    "        img_list = os.listdir(os.path.join(img_path, cate, file))\n",
    "        img_list.sort()\n",
    "        batch_img = []\n",
    "        for i in range(batch_size):\n",
    "            path = os.path.join(img_path, cate, file)\n",
    "            label =  int(classes_dict[cate])-1\n",
    "            symbol = len(img_list)//16\n",
    "            if train:\n",
    "                # 随机进行裁剪\n",
    "                crop_x = random.randint(0, 15)\n",
    "                crop_y = random.randint(0, 58)\n",
    "                # 随机进行翻转\n",
    "                is_flip = random.randint(0, 1)\n",
    "                # 以16 帧为单位\n",
    "                for j in range(16):\n",
    "                    img = img_list[symbol + j]\n",
    "                    image = cv2.imread( path + '/' + img)\n",
    "                    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n",
    "                    image = cv2.resize(image, (171, 128))\n",
    "                    if is_flip == 1:\n",
    "                        image = cv2.flip(image, 1)\n",
    "                    batch[i][j][:][:][:] = image[crop_x:crop_x + 112, crop_y:crop_y + 112, :]\n",
    "                    symbol-=1\n",
    "                    if symbol<0:\n",
    "                        break\n",
    "                labels[i] = label\n",
    "            else:\n",
    "                for j in range(16):\n",
    "                    img = img_list[symbol + j]\n",
    "                    image = cv2.imread( path + '/' + img)\n",
    "                    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n",
    "                    image = cv2.resize(image, (171, 128))\n",
    "                    batch[i][j][:][:][:] = image[8:120, 30:142, :]\n",
    "                    symbol-=1\n",
    "                    if symbol<0:\n",
    "                        break\n",
    "                labels[i] = label\n",
    "    return batch, labels"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "每个batch的形状为：(16, 16, 112, 112, 3)\n",
      "每个label的形状为：(16,)\n"
     ]
    }
   ],
   "source": [
    "batch, labels = process_data(img_path, train_list)\n",
    "\n",
    "print(\"每个batch的形状为：%s\"%(str(batch.shape)))\n",
    "print(\"每个label的形状为：%s\"%(str(labels.shape)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "定义data generator， 将数据批次传入训练函数中。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "def generator_train_batch(train_list, batch_size, num_classes, img_path):\n",
    "    while True:\n",
    "        # 读取一个batch的数据\n",
    "        x_train, x_labels = process_data(img_path, train_list, batch_size=16,train=True)\n",
    "        x = preprocess(x_train)\n",
    "        # 形成input要求的数据格式\n",
    "        y = np_utils.to_categorical(np.array(x_labels), num_classes)\n",
    "        x = np.transpose(x, (0,2,3,1,4))\n",
    "        yield x, y\n",
    "def generator_val_batch(test_list, batch_size, num_classes, img_path):\n",
    "    while True:\n",
    "        # 读取一个batch的数据\n",
    "        y_test,y_labels = process_data(img_path, train_list, batch_size=16,train=False)\n",
    "        x = preprocess(y_test)\n",
    "        # 形成input要求的数据格式\n",
    "        x = np.transpose(x,(0,2,3,1,4))\n",
    "        y = np_utils.to_categorical(np.array(y_labels), num_classes)\n",
    "        yield x, y"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "定义方法`preprocess`， 对函数的输入数据进行图像的标准化处理。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "def preprocess(inputs):\n",
    "    inputs[..., 0] -= 99.9\n",
    "    inputs[..., 1] -= 92.1\n",
    "    inputs[..., 2] -= 82.6\n",
    "    inputs[..., 0] /= 65.8\n",
    "    inputs[..., 1] /= 62.3\n",
    "    inputs[..., 2] /= 60.3\n",
    "    return inputs"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "WARNING:tensorflow:From /home/ma-user/anaconda3/envs/TensorFlow-1.13.1/lib/python3.6/site-packages/tensorflow/python/ops/math_ops.py:3066: to_int32 (from tensorflow.python.ops.math_ops) is deprecated and will be removed in a future version.\n",
      "Instructions for updating:\n",
      "Use tf.cast instead.\n",
      "Epoch 1/1\n",
      "18/18 [==============================] - 234s 13s/step - loss: 28.5715 - acc: 0.9340 - val_loss: 27.7766 - val_acc: 1.0000\n"
     ]
    }
   ],
   "source": [
    "# 训练一个epoch大约需4分钟\n",
    "# 类别数量\n",
    "num_classes = 101\n",
    "# batch大小\n",
    "batch_size = 4\n",
    "# epoch数量\n",
    "epochs = 1\n",
    "# 学习率大小\n",
    "lr = 0.005\n",
    "# 优化器定义\n",
    "sgd = SGD(lr=lr, momentum=0.9, nesterov=True)\n",
    "model.compile(loss='categorical_crossentropy', optimizer=sgd, metrics=['accuracy'])\n",
    "# 开始训练\n",
    "history = model.fit_generator(generator_train_batch(train_list, batch_size, num_classes,img_path),\n",
    "                              steps_per_epoch= len(train_list) // batch_size,\n",
    "                              epochs=epochs,\n",
    "                              callbacks=[onetenth_4_8_12(lr)],\n",
    "                              validation_data=generator_val_batch(test_list, batch_size,num_classes,img_path),\n",
    "                              validation_steps= len(test_list) // batch_size,\n",
    "                              verbose=1)\n",
    "# 对训练结果进行保存\n",
    "model.save_weights(os.path.join(results_path, 'weights_c3d.h5'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 模型测试\n",
    "接下来我们将训练之后得到的模型进行测试。随机在UCF-101中选择一个视频文件作为测试数据，然后对视频进行取帧，每16帧画面传入模型进行一次动作预测，并且将动作预测以及预测百分比打印在画面中并进行视频播放。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "首先，引入相关的库。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "from IPython.display import clear_output, Image, display, HTML\n",
    "import time\n",
    "import cv2\n",
    "import base64\n",
    "import numpy as np"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "构建模型结构并且加载权重。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "from models import c3d_model\n",
    "model = c3d_model()\n",
    "model.load_weights(os.path.join(results_path, 'weights_c3d.h5'), by_name=True)  # 加载刚训练的模型"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "定义函数arrayshow，进行图片变量的编码格式转换。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "def arrayShow(img):\n",
    "    _,ret = cv2.imencode('.jpg', img) \n",
    "    return Image(data=ret) "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "进行视频的预处理以及预测，将预测结果打印到画面中，最后进行播放。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 加载所有的类别和编号\n",
    "with open('./ucfTrainTestlist/classInd.txt', 'r') as f:\n",
    "    class_names = f.readlines()\n",
    "    f.close()\n",
    "# 读取视频文件\n",
    "video = './videos/v_Punch_g03_c01.avi'\n",
    "cap = cv2.VideoCapture(video)\n",
    "clip = []\n",
    "# 将视频画面传入模型\n",
    "while True:\n",
    "    try:\n",
    "        clear_output(wait=True)\n",
    "        ret, frame = cap.read()\n",
    "        if ret:\n",
    "            tmp = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)\n",
    "            clip.append(cv2.resize(tmp, (171, 128)))\n",
    "            # 每16帧进行一次预测\n",
    "            if len(clip) == 16:\n",
    "                inputs = np.array(clip).astype(np.float32)\n",
    "                inputs = np.expand_dims(inputs, axis=0)\n",
    "                inputs[..., 0] -= 99.9\n",
    "                inputs[..., 1] -= 92.1\n",
    "                inputs[..., 2] -= 82.6\n",
    "                inputs[..., 0] /= 65.8\n",
    "                inputs[..., 1] /= 62.3\n",
    "                inputs[..., 2] /= 60.3\n",
    "                inputs = inputs[:,:,8:120,30:142,:]\n",
    "                inputs = np.transpose(inputs, (0, 2, 3, 1, 4))\n",
    "                # 获得预测结果\n",
    "                pred = model.predict(inputs)\n",
    "                label = np.argmax(pred[0])\n",
    "                # 将预测结果绘制到画面中\n",
    "                cv2.putText(frame, class_names[label].split(' ')[-1].strip(), (20, 20),\n",
    "                            cv2.FONT_HERSHEY_SIMPLEX, 0.6,\n",
    "                            (0, 0, 255), 1)\n",
    "                cv2.putText(frame, \"prob: %.4f\" % pred[0][label], (20, 40),\n",
    "                            cv2.FONT_HERSHEY_SIMPLEX, 0.6,\n",
    "                            (0, 0, 255), 1)\n",
    "                clip.pop(0)\n",
    "            # 播放预测后的视频    \n",
    "            lines, columns, _ = frame.shape\n",
    "            frame = cv2.resize(frame, (int(columns), int(lines)))\n",
    "            img = arrayShow(frame)\n",
    "            display(img)\n",
    "            time.sleep(0.02)\n",
    "        else:\n",
    "            break\n",
    "    except:\n",
    "        print(0)\n",
    "cap.release()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## I3D 模型"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在之前我们简单介绍了I3D模型，[I3D官方github库](https://github.com/deepmind/kinetics-i3d)提供了在Kinetics上预训练的模型和预测代码，接下来我们将体验I3D模型如何对视频进行预测。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "首先，引入相关的包"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "WARNING: The TensorFlow contrib module will not be included in TensorFlow 2.0.\n",
      "For more information, please see:\n",
      "  * https://github.com/tensorflow/community/blob/master/rfcs/20180907-contrib-sunset.md\n",
      "  * https://github.com/tensorflow/addons\n",
      "If you depend on functionality not listed there, please file an issue.\n",
      "\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "import tensorflow as tf\n",
    "\n",
    "import i3d"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "进行参数的定义"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 输入图片大小\n",
    "_IMAGE_SIZE = 224\n",
    "#  视频的帧数\n",
    "_SAMPLE_VIDEO_FRAMES = 79\n",
    "\n",
    "# 输入数据包括两部分：RGB和光流\n",
    "# RGB和光流数据已经经过提前计算\n",
    "_SAMPLE_PATHS = {\n",
    "    'rgb': 'data/v_CricketShot_g04_c01_rgb.npy',\n",
    "    'flow': 'data/v_CricketShot_g04_c01_flow.npy',\n",
    "}\n",
    "# 提供了多种可以选择的预训练权重\n",
    "# 其中，imagenet系列模型从ImageNet的2D权重中拓展而来，其余为视频数据下的预训练权重\n",
    "_CHECKPOINT_PATHS = {\n",
    "    'rgb': 'data/checkpoints/rgb_scratch/model.ckpt',\n",
    "    'flow': 'data/checkpoints/flow_scratch/model.ckpt',\n",
    "    'rgb_imagenet': 'data/checkpoints/rgb_imagenet/model.ckpt',\n",
    "    'flow_imagenet': 'data/checkpoints/flow_imagenet/model.ckpt',\n",
    "}\n",
    "# 记录类别文件\n",
    "_LABEL_MAP_PATH = 'data/label_map.txt'\n",
    "# 类别数量为400\n",
    "NUM_CLASSES = 400"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "定义参数：\n",
    "- imagenet_pretrained ：如果为`True`，则调用预训练权重，如果为`False`，则调用ImageNet转成的权重"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [],
   "source": [
    "imagenet_pretrained = True"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 加载动作类型\n",
    "kinetics_classes = [x.strip() for x in open(_LABEL_MAP_PATH)]\n",
    "tf.logging.set_verbosity(tf.logging.INFO)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "构建RGB部分模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [],
   "source": [
    "rgb_input = tf.placeholder(tf.float32, shape=(1, _SAMPLE_VIDEO_FRAMES, _IMAGE_SIZE, _IMAGE_SIZE, 3))\n",
    "\n",
    "with tf.variable_scope('RGB', reuse=tf.AUTO_REUSE):\n",
    "    rgb_model = i3d.InceptionI3d(NUM_CLASSES, spatial_squeeze=True, final_endpoint='Logits')\n",
    "    rgb_logits, _ = rgb_model(rgb_input, is_training=False, dropout_keep_prob=1.0)\n",
    "\n",
    "rgb_variable_map = {}\n",
    "for variable in tf.global_variables():\n",
    "    if variable.name.split('/')[0] == 'RGB':\n",
    "        rgb_variable_map[variable.name.replace(':0', '')] = variable\n",
    "        \n",
    "rgb_saver = tf.train.Saver(var_list=rgb_variable_map, reshape=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "构建光流部分模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [],
   "source": [
    "flow_input = tf.placeholder(tf.float32,shape=(1, _SAMPLE_VIDEO_FRAMES, _IMAGE_SIZE, _IMAGE_SIZE, 2))\n",
    "\n",
    "with tf.variable_scope('Flow', reuse=tf.AUTO_REUSE):\n",
    "    flow_model = i3d.InceptionI3d(NUM_CLASSES, spatial_squeeze=True, final_endpoint='Logits')\n",
    "    flow_logits, _ = flow_model(flow_input, is_training=False, dropout_keep_prob=1.0)\n",
    "flow_variable_map = {}\n",
    "for variable in tf.global_variables():\n",
    "    if variable.name.split('/')[0] == 'Flow':\n",
    "        flow_variable_map[variable.name.replace(':0', '')] = variable\n",
    "flow_saver = tf.train.Saver(var_list=flow_variable_map, reshape=True)    "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "将模型联合，成为完整的I3D模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [],
   "source": [
    "model_logits = rgb_logits + flow_logits\n",
    "model_predictions = tf.nn.softmax(model_logits)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "开始模型预测,获得视频动作预测结果。\n",
    "预测数据为开篇提供的RGB和光流数据：\n",
    "\n",
    "![See videos/v_CricketShot_g04_c01_rgb.gif](./img/v_CricketShot_g04_c01_rgb.gif)\n",
    "\n",
    "![See videos/v_CricketShot_g04_c01_flow.gif](./img/v_CricketShot_g04_c01_flow.gif)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "WARNING:tensorflow:From /home/ma-user/anaconda3/envs/TensorFlow-1.13.1/lib/python3.6/site-packages/tensorflow/python/training/saver.py:1266: checkpoint_exists (from tensorflow.python.training.checkpoint_management) is deprecated and will be removed in a future version.\n",
      "Instructions for updating:\n",
      "Use standard file APIs to check for files with this prefix.\n",
      "INFO:tensorflow:Restoring parameters from data/checkpoints/rgb_imagenet/model.ckpt\n",
      "INFO:tensorflow:RGB checkpoint restored\n",
      "INFO:tensorflow:RGB data loaded, shape=(1, 79, 224, 224, 3)\n",
      "INFO:tensorflow:Restoring parameters from data/checkpoints/flow_imagenet/model.ckpt\n",
      "INFO:tensorflow:Flow checkpoint restored\n",
      "INFO:tensorflow:Flow data loaded, shape=(1, 79, 224, 224, 2)\n",
      "Norm of logits: 138.468674\n",
      "\n",
      "Top classes and probabilities\n",
      "1.0 41.81368 playing cricket\n",
      "1.497162e-09 21.493984 hurling (sport)\n",
      "3.8430945e-10 20.134106 catching or throwing baseball\n",
      "1.5492242e-10 19.225582 catching or throwing softball\n",
      "1.1360122e-10 18.915352 hitting baseball\n",
      "8.801155e-11 18.660126 playing tennis\n",
      "2.4415558e-11 17.377878 playing kickball\n",
      "1.1532015e-11 16.627785 playing squash or racquetball\n",
      "6.1319123e-12 15.996164 shooting goal (soccer)\n",
      "4.391744e-12 15.662385 hammer throw\n",
      "2.2134267e-12 14.977199 golf putting\n",
      "1.6307064e-12 14.671671 throwing discus\n",
      "1.545613e-12 14.618078 javelin throw\n",
      "7.669003e-13 13.91726 pumping fist\n",
      "5.192929e-13 13.527371 shot put\n",
      "4.2681177e-13 13.331245 celebrating\n",
      "2.7205357e-13 12.880902 applauding\n",
      "1.8357015e-13 12.4875 throwing ball\n",
      "1.6134572e-13 12.358452 dodgeball\n",
      "1.1388438e-13 12.010085 tap dancing\n"
     ]
    }
   ],
   "source": [
    "with tf.Session() as sess:\n",
    "    feed_dict = {}\n",
    "    \n",
    "    if imagenet_pretrained:\n",
    "        rgb_saver.restore(sess, _CHECKPOINT_PATHS['rgb_imagenet'])    # 加载rgb流的模型\n",
    "    else:\n",
    "        rgb_saver.restore(sess, _CHECKPOINT_PATHS['rgb'])\n",
    "    tf.logging.info('RGB checkpoint restored')\n",
    "    \n",
    "    if imagenet_pretrained:\n",
    "        flow_saver.restore(sess, _CHECKPOINT_PATHS['flow_imagenet'])  # 加载flow流的模型\n",
    "    else:\n",
    "        flow_saver.restore(sess, _CHECKPOINT_PATHS['flow'])\n",
    "    tf.logging.info('Flow checkpoint restored')   \n",
    "    \n",
    "    start_time = time.time()\n",
    "    \n",
    "    rgb_sample = np.load(_SAMPLE_PATHS['rgb'])    # 加载rgb流的输入数据\n",
    "    tf.logging.info('RGB data loaded, shape=%s', str(rgb_sample.shape))\n",
    "    feed_dict[rgb_input] = rgb_sample\n",
    "       \n",
    "    flow_sample = np.load(_SAMPLE_PATHS['flow'])  # 加载flow流的输入数据\n",
    "    tf.logging.info('Flow data loaded, shape=%s', str(flow_sample.shape))\n",
    "    feed_dict[flow_input] = flow_sample\n",
    "\n",
    "    out_logits, out_predictions = sess.run(\n",
    "        [model_logits, model_predictions],\n",
    "        feed_dict=feed_dict)\n",
    "\n",
    "    out_logits = out_logits[0]\n",
    "    out_predictions = out_predictions[0]\n",
    "    sorted_indices = np.argsort(out_predictions)[::-1]\n",
    "    print('Inference time in sec: %.3f' % float(time.time() - start_time))\n",
    "    print('Norm of logits: %f' % np.linalg.norm(out_logits))\n",
    "    \n",
    "    print('\\nTop classes and probabilities')\n",
    "    for index in sorted_indices[:20]:\n",
    "        print(out_predictions[index], out_logits[index], kinetics_classes[index])"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.3"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
